Skip to content

feat(security): warn once when enableSecureText is disabled + document XSS trade-off#480

Merged
gnbm merged 7 commits into
masterfrom
security/secure-text-warning-only
Jun 10, 2026
Merged

feat(security): warn once when enableSecureText is disabled + document XSS trade-off#480
gnbm merged 7 commits into
masterfrom
security/secure-text-warning-only

Conversation

@gnbm

@gnbm gnbm commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Problem

enableSecureText defaults to false, so option text (label, value, description, and any customData interpolated into markup such as the option aria-label) is written to the DOM as raw HTML. Consumers rendering untrusted data without opting in are exposed to XSS, and there is no signal that this is happening.

Changing the default to true would be disruptive (breaks intentional-HTML consumers) and would impose per-option escaping cost on large datasets (10k–100k+ records) where it is often unnecessary. So this PR keeps the default off but makes the trade-off impossible to miss.

Change

  • One-time console warning: when an instance is initialized while enableSecureText is disabled, log a single (per page) console.warn pointing to the property docs. It fires on the configuration alone at construction — it is O(1) and never scans option content, so there is no cost on large datasets, and it is not missed when options are loaded later (e.g. via setOptions or server search). Tracked by a static secureTextWarningShown flag.
  • Docs: a prominent security callout at the top of properties.md plus an expanded enableSecureText row. The docs are explicit about scope: enableSecureText escapes the built-in fields (label, value, description); customData is stored as-is except where the library itself interpolates it, and HTML returned by labelRenderer/selectedLabelRenderer is not sanitized.

No behavioural/default change beyond the warning.

Opt-out: showSecureTextWarning

New per-instance boolean option showSecureTextWarning (default true). Set it to false to suppress the one-time warning once you have consciously accepted the trade-off (trusted/developer-controlled option data, or intentional HTML) — without paying for escaping via enableSecureText.

It is a dedicated, clearly-named option rather than an overload of the internal secureTextWarningShown lifecycle flag, and is wired through the standard plumbing (dataProps, setDefaultProps, setProps, data-show-secure-text-warning attribute mapping) plus the virtualSelectOptions typedef and properties.md.

Verification

  • npm run validate (tsc + eslint + stylelint): clean
  • npm run build: succeeds; dist/docs bundles rebuilt
  • Spec cypress/e2e/secure-text-warning.cy.ts4 passing: warns exactly once across two instances; does not warn when enableSecureText: true; does not warn when showSecureTextWarning: false; warns even when constructed with empty options.
  • No regressions in the functional examples suite.

…t XSS trade-off

enableSecureText defaults to false, so option label/value are written to the
DOM as raw HTML. Consumers rendering untrusted data without opting in are
exposed to XSS, with no signal that it is happening.

Keep the default off (changing it would break intentional-HTML consumers and
impose per-option escaping cost on large datasets), but make the trade-off
discoverable:

- One-time (per page) console.warn when options render while enableSecureText
  is disabled. O(1) static-flag guard; never scans option content, so there is
  no cost on large datasets.
- properties.md: prominent security callout + expanded enableSecureText row.

No behavioural/default change beyond the warning.

Verified: npm run validate (tsc/eslint/stylelint) clean; npm run build succeeds;
new spec secure-text-warning.cy.ts passes; existing security/perf/lifecycle
specs (16 tests) still pass.
Adds a per-instance `showSecureTextWarning` boolean (default `true`) so a
consumer who has consciously accepted the enableSecureText trade-off (trusted
/ developer-controlled option data, or intentional HTML) can silence the
one-time console warning without enabling escaping.

- New prop wired through the standard plumbing: dataProps registration,
  setDefaultProps default (true), setProps assignment, and the
  data-show-secure-text-warning attribute mapping.
- warnIfSecureTextDisabled() now also returns early when the prop is false.
  Kept as a dedicated, clearly-named option rather than overloading the
  internal secureTextWarningShown lifecycle flag.
- Documented in properties.md (new row + callout note) and the
  virtualSelectOptions typedef.

Verified: npm run validate clean; npm run build succeeds; new test case asserts
showSecureTextWarning:false suppresses the warning; full suite 222 tests pass
(secure-text-warning 3, security specs 4, examples 215) - no regressions.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to make the XSS trade-off of leaving enableSecureText disabled (the default) more discoverable by adding a one-time console warning and prominent documentation, while preserving current default behavior for performance reasons on large datasets.

Changes:

  • Add a per-page, one-time console.warn when rendering options with enableSecureText: false, with a per-instance opt-out via showSecureTextWarning.
  • Document the XSS/performance trade-off prominently in docs/properties.md, including the new showSecureTextWarning property.
  • Add Cypress coverage verifying the warning fires once, and is suppressed when enableSecureText: true or showSecureTextWarning: false.

Reviewed changes

Copilot reviewed 5 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/virtual-select.types.js Extends the public options typedef with showSecureTextWarning.
src/virtual-select.js Implements the one-time warning flag + new prop plumbing.
docs/properties.md Adds a security callout and documents the new warning behavior/option.
cypress/e2e/secure-text-warning.cy.ts Adds E2E coverage for the one-time warning behavior.
docs/assets/virtual-select.js Generated docs bundle update reflecting new behavior.
dist/virtual-select.js Generated distribution bundle update reflecting new behavior.
dist/virtual-select.min.js Generated minified distribution bundle update reflecting new behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/virtual-select.js Outdated
Comment thread src/virtual-select.js Outdated
Comment thread docs/properties.md Outdated
Comment thread docs/properties.md Outdated
Comment thread docs/assets/virtual-select.js
… message & docs

- warnIfSecureTextDisabled(): drop the empty-options guard so the once-per-page
  warning fires on configuration at construction. Previously a select initialised
  with no options (then populated later via setOptions/server search) never warned
  even though its option text renders as raw HTML. (Copilot: Medium)
- warning message: broaden to cover label, value, description and customData used in
  markup (not just label/value). Trailing comma kept intentionally - airbnb-base
  comma-dangle requires it and the bundle is transpiled via @babel/preset-env to a
  browserslist that excludes IE11, so the IE11 parsing concern does not apply here.
  (Copilot: High - message reworded; IE11/trailing-comma part rejected)
- docs/properties.md: stop claiming enableSecureText escapes 'customData fields'.
  It escapes the built-in fields (label, value, description); customData is stored
  as-is except where the library interpolates it, and labelRenderer/selectedLabelRenderer
  output is not sanitized. (Copilot: High x2)
- test: add coverage asserting the warning fires when constructed with empty options.

Bundles regenerated; npm run validate + build pass; secure-text-warning spec 4/4 green.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 8 changed files in this pull request and generated 2 comments.

Comment thread docs/properties.md Outdated
Comment thread docs/assets/virtual-select.js
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 8 changed files in this pull request and generated 3 comments.

Comment thread src/virtual-select.js
Comment thread docs/assets/virtual-select.js
Comment thread src/virtual-select.types.js Outdated
gnbm and others added 3 commits June 10, 2026 16:06
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…nchor)

Copilot review (Medium): the `?id=enablesecuretext` fragment does not resolve -
docsify generates anchors from headings, but enableSecureText is a table row, not
a heading (the repo's working ?id= links all target real headings). The security
callout is at the top of properties.md, so the bare #/properties link lands the
reader on it. Trailing comma kept intentionally (airbnb comma-dangle requires it;
IE11 excluded from browserslist; bundle is transpiled), so the comma-removal part
of the suggestion was not applied.

Bundles regenerated; validate + build pass; secure-text-warning spec 4/4.
@gnbm gnbm merged commit 4c28446 into master Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants